Skip to content

Fix guides index: surface every YAML entry, drop legacy categories, curate tag cloud#504

Merged
jamesfredley merged 1 commit intomasterfrom
fix-guides-index-page
May 4, 2026
Merged

Fix guides index: surface every YAML entry, drop legacy categories, curate tag cloud#504
jamesfredley merged 1 commit intomasterfrom
fix-guides-index-page

Conversation

@jamesfredley
Copy link
Copy Markdown
Contributor

Summary

Five recently-added Grails 8 guides plus three older entries were silently absent from https://grails.apache.org/guides/ even though their per-guide HTML was being built and uploaded correctly. Three independent bugs combined to cause this, and the bottom-of-page category grid was simultaneously overweight on dead/legacy stacks while missing the modern Web Layer section that every flagship Grails 8 guide lives in.

This PR fixes the index page, refactors the fetcher to be structurally robust against future YAML drift, and reshapes the category/tag presentation around what Apache Grails actually publishes today. All four fixes in one PR per request.

Net diff: 4 files changed, +451 / -224.

Before vs after (local build/dist/guides/index.html)

Guide Before After
grails-multi-module/v8 0 hits 2 hits
grails-rest-library/v8 0 hits 2 hits
grails-github-actions-cicd/v8 0 hits 2 hits
grails-as-docker-container/v3,v4 0 hits 2 hits
grails-mock-basics/v4 0 (only v3 listed) 2 (v3+v4)
creating-your-first-grails-app/v6 0 (only v3+v4) 3 (v3+v4+v6)
rest-hibernate / grails-on-github-actions / grails-multi-project-build mis-rendered as the wrong v8 guide each correct

The Latest Guides sidebar now contains all 8 new Grails 8 guides (was 5 of 8). The tag cloud is now 50 entries (was 200+).

What this PR does

1. conf/guides.yml - 5 sampleRef.repo collisions

Five YAML entries had sampleRef.repo pointing at a NEW guide's repo instead of their own. The fetcher used to group by sampleRef.repo, so they merged into the wrong guide:

  grails-as-docker-container v3, v4
- repo: 'grails-guides/grails-docker-bootbuildimage'
+ repo: 'grails-guides/grails-as-docker-container'

  grails-multi-project-build v4
- repo: 'grails-guides/grails-multi-module'
+ repo: 'grails-guides/grails-multi-project-build'

  rest-hibernate v3, v4
- repo: 'grails-guides/grails-rest-library'
+ repo: 'grails-guides/rest-hibernate'

  grails-on-github-actions v4
- repo: 'grails-guides/grails-github-actions-cicd'
+ repo: 'grails-guides/grails-on-github-actions'

These look like the residue of repo renames that were applied in the sample registry but never propagated to the new entries that needed distinct slugs. Each of the GitHub URLs above 301-redirects today, so "Get the Code" buttons remain functional regardless.

2. GuidesFetcher - identify guides by YAML name, not sampleRef.repo

Even with the YAML cleaned up, the fetcher was structurally fragile and had two latent bugs:

  1. BRANCH_TO_MAJOR map hardcoded master -> 4 and was missing grails7, grails8. creating-your-first-grails-app/v6 (whose sampleRef.branch is master) was silently mapped to major version 4, overwriting the real v4 entry in grailsMayorVersionTags. v6 was reachable only via direct URL.
  2. grails-mock-basics had v3 and v4 both with branch: master under one guide. The slug+branch grouping reduced to {master}, so size == 1 triggered toSingleGuide and silently returned the first match (v3).

Refactor parseGuides to walk one Guide per top-level YAML entry:

  • 1 versions: child → SingleGuide
  • 2+ versions: children → GrailsVersionedGuide with one rendered link per version. The YAML version key is now used directly as the integer major version, which eliminates BRANCH_TO_MAJOR entirely.

sampleRef.repo is now treated only as the "Get the Code" target on the rendered chrome - it is never used to identify or group guides. Two YAML entries that happen to point at the same external repo are still rendered as two separate guides.

New GuidesFetcherSpec (7 tests) pins all the regression cases:

  • SingleGuide for 1-version entries
  • GrailsVersionedGuide for 2+-version entries with the YAML version key preserved as the int major version (master->4 + grails7/8 regression)
  • Two YAML entries sharing sampleRef.repo render as two guides (4-collision regression)
  • Two YAML versions sharing the same branch are both kept when they belong to the same guide (mock-basics regression)
  • Future-dated guides are filtered when skipFuture is true
  • Empty versions: blocks are silently skipped
  • Sort order is publication-date-descending

3. GuidesPage.categories - drop dead categories, add Web Layer

Dropped: Grails + Android, Grails + Angular, Grails + AngularJS, Grails + iOS, Grails + RIA (Rich Internet Application). Each had 1-2 stale guides and was eating equal real estate to Advanced Grails (22 guides).

Added: Web Layer (5 guides today: htmx, tailwindcss, vite-spa, fields-custom-widgets-and-wrappers, rest-library). Without this section, rest-library was the only one of those guides that didn't fit in the top-8 Latest Guides sidebar, so it was completely invisible.

Reshuffled the two-column grid so the modern stacks sit above the older ones:

| Grails Apprentice  | Advanced Grails  |
| Grails Async       |                  |
| Web Layer          | Grails + DevOps  |    <- both new positions
| GORM               | Grails Testing   |
| Grails + Vue.js    | Grails + React   |
| Grails + Google Cloud |               |

Guides categorized in the dropped categories remain built and reachable via tags, search, and direct URL. Only the index-page section is gone. Their categories/<slug>.html pages also stop being generated.

4. tagCloud - filter version labels, cap to top N

The Guides by Tag cloud rendered every tag in the YAML, alphabetically, with no curation. Two problems:

  • Version-label tags (grails3, grails4, ..., grails8) artificially dominated the cloud by occurrence count without adding navigational value (the version is implicit in the guide URL).
  • The long tail of one-shot legacy tags (apple-tv, tvml, tvmljs, aws-elasticbeanstlak, ...) cluttered the sidebar.

Now: filters out version labels via ~/^grails\d+$/, sorts by occurrence descending, takes top TAG_CLOUD_LIMIT = 50, then re-sorts alphabetically for display.

Verification

./gradlew :buildSrc:test --tests 'website.model.guides.GuidesFetcherSpec'
  -> all 7 pass

./gradlew build validateGuides
  -> BUILD SUCCESSFUL, validateGuides 93 guide(s) parsed, 0 errors

What's intentionally still NOT linked from the index

After the dead-category drop, 7 guide-version pairs no longer have a category section to live in: angular2-combined/v4, building-an-android-client-..., building-an-ios-objectc-client-..., building-an-ios-swift-client-..., grails-quickcasts-angularjs-scaffolding-with-grails-3/v4, grails-restapi-angularjs/v4, vaadin-grails/v4. Their per-guide HTML still builds. They remain reachable via tag pages, search, and direct URL. This is the user's chosen "drop dead categories" approach.

…urate tag cloud

Five recently-added Grails 8 guides (multi-module, rest-library, github-actions-cicd)
plus three older entries (grails-as-docker-container, grails-mock-basics v4,
creating-your-first-grails-app v6) were silently absent from
https://grails.apache.org/guides/ even though their per-guide HTML was
built and uploaded correctly. Three independent bugs combined to make
this happen, and the bottom-of-page category grid was simultaneously
overweight on dead/legacy stacks while missing the modern Web Layer
section that every flagship Grails 8 guide lives in.

== conf/guides.yml: 5 sampleRef.repo collisions ==

The fetcher used to group guide-versions by sampleRef.repo, so any two
YAML entries that happened to point at the same external repo merged
into a single rendered guide and clobbered each other. Five YAML entries
were pointing at the wrong repo (the new Grails 8 sample repo instead of
their own legacy repo):

  - grails-as-docker-container v3, v4 -> grails-as-docker-container
    (was grails-docker-bootbuildimage; collided with grails-docker-bootbuildimage v8)
  - grails-multi-project-build v4 -> grails-multi-project-build
    (was grails-multi-module; collided with grails-multi-module v8)
  - rest-hibernate v3, v4 -> rest-hibernate
    (was grails-rest-library; collided with grails-rest-library v8)
  - grails-on-github-actions v4 -> grails-on-github-actions
    (was grails-github-actions-cicd; collided with grails-github-actions-cicd v8)

These look like the residue of repo renames that were applied in the
sample registry but never propagated to the new entries that needed
distinct slugs.

== GuidesFetcher: identify guides by YAML name, not sampleRef.repo ==

Even with the YAML cleaned up, the fetcher was structurally fragile.
It also had two latent bugs:

1. BRANCH_TO_MAJOR map hardcoded master->4 and was missing grails7,
   grails8. creating-your-first-grails-app/v6 (whose sampleRef.branch
   is master) was silently mapped to major version 4, overwriting the
   real v4 entry in grailsMayorVersionTags. The v6 build was reachable
   only via direct URL.
2. grails-mock-basics had v3 and v4 both with branch=master under one
   guide. The slug+branch grouping reduced to {master}, so size==1
   triggered toSingleGuide and silently returned the first match (v3).

Refactor parseGuides to walk one Guide per top-level YAML entry:

  - 1 versions: child  -> SingleGuide
  - 2+ versions: child -> GrailsVersionedGuide, with one rendered link
                          per version. The YAML version key is used
                          DIRECTLY as the integer major version, which
                          eliminates BRANCH_TO_MAJOR entirely.

sampleRef.repo is now treated only as the "Get the Code" target on the
rendered chrome - it is never used to identify or group guides. Two
YAML entries that happen to point at the same external repo are still
rendered as two separate guides.

Adds GuidesFetcherSpec with 7 unit tests pinning the regression cases:

  - SingleGuide for 1-version entries
  - GrailsVersionedGuide for 2+-version entries with the YAML version
    key preserved as the int major version (covers the master->4
    overwrite + missing grails7/grails8)
  - Two YAML entries sharing sampleRef.repo render as two guides
    (covers all 4 collision pairs above)
  - Two YAML versions sharing the same branch are both kept when they
    belong to the same guide (covers grails-mock-basics)
  - Future-dated guides are filtered when skipFuture is true
  - Empty versions: blocks are silently skipped
  - Sort order is publication-date-descending

== GuidesPage: trim dead categories, add Web Layer, curate tag cloud ==

categories map: dropped Grails + Android, Grails + Angular,
Grails + AngularJS, Grails + iOS, Grails + RIA. Each had 1-2 stale
guides and was eating equal real estate to Advanced Grails (22 guides).
Their guides remain built and reachable via tags, search, and direct
URL; only the index-page section is gone.

Added Web Layer (5 guides today: htmx, tailwindcss, vite-spa,
fields-custom-widgets-and-wrappers, rest-library). Without this
section, rest-library was the only one of those guides that didn't
even fit in the top-8 Latest Guides sidebar, so it was completely
invisible from the index.

Reshuffled the two-column grid so the modern stacks (Apprentice,
Advanced, Web Layer, DevOps) sit above the older ones (GORM, Testing,
Vue, React, Google Cloud). DevOps in particular was previously buried
under the legacy iOS / Android / RIA cluster despite having more guides
than any of them.

tagCloud: filters out version-label tags (grails3..grails8) - they
artificially dominated the cloud by occurrence count without adding
navigational value (the version is implicit in the guide URL anyway).
Caps the visible set to TAG_CLOUD_LIMIT=50 most-used tags before
re-sorting alphabetically for display, so the long tail of one-shot
legacy tags (apple-tv, tvml, aws-elasticbeanstlak, ...) no longer
clutters the sidebar.

== Verified ==

  ./gradlew :buildSrc:test --tests 'website.model.guides.GuidesFetcherSpec'
    -> all 7 pass

  ./gradlew build validateGuides
    -> BUILD SUCCESSFUL, validateGuides 93 guide(s) parsed, 0 errors

Local build/dist/guides/index.html before-vs-after:
  - multi-module      0 -> 2 hits
  - rest-library      0 -> 2 hits
  - github-actions-cicd                 0 -> 2 hits
  - grails-as-docker-container          0 -> 2 hits
  - grails-mock-basics  1 (v3 only) -> 2 (v3 + v4)
  - creating-your-first-grails-app  2 (v3,v4) -> 3 (v3,v4,v6)
  - rest-hibernate / grails-on-github-actions / grails-multi-project-build:
    each one stops being mis-rendered as the wrong v8 guide

Latest Guides sidebar now contains all 8 new Grails 8 guides
(was 5 of 8: docker-bootbuildimage, htmx, spock-test-tour, tailwindcss,
vite-spa). Tag cloud is now 50 entries (was 200+).

Assisted-by: claude-code:claude-opus-4-7
Copilot AI review requested due to automatic review settings May 4, 2026 00:43
@jamesfredley jamesfredley merged commit 18dfc51 into master May 4, 2026
5 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes guide-index rendering so every YAML-defined guide entry appears correctly on /guides, while also reshaping the index taxonomy and tag cloud for the current Grails guide catalog. It does this in the guide metadata source, the YAML fetcher/model layer, and the page renderer used by the guides site build.

Changes:

  • Corrects several sampleRef.repo values in conf/guides.yml so legacy guides no longer collide with newer guides during index generation.
  • Refactors GuidesFetcher to build guides from top-level YAML name entries instead of grouping by repository slug, and adds regression tests for the reported edge cases.
  • Curates the guides index presentation by removing legacy categories, adding a Web Layer category, and filtering/capping the tag cloud.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
conf/guides.yml Fixes misassigned guide sample repositories for several affected guide entries.
buildSrc/src/test/groovy/website/model/guides/GuidesFetcherSpec.groovy Adds regression coverage for single-version, multi-version, collision, duplicate-branch, future-date, empty-version, and sort-order cases.
buildSrc/src/main/groovy/website/model/guides/GuidesPage.groovy Updates index category layout and adds tag-cloud curation/filtering.
buildSrc/src/main/groovy/website/model/guides/GuidesFetcher.groovy Reworks YAML parsing/grouping to construct guides per top-level entry using YAML version keys directly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 60 to +70
static Map<String, Category> categories = [
advanced: new Category(name: 'Advanced Grails', image: 'advancedgrails.svg'),
android: new Category(name: 'Grails + Android', image: 'grails_android.svg'),
angular: new Category(name: 'Grails + Angular', image: 'grailsangular.svg'),
angularjs: new Category(name: 'Grails + AngularJS', image: 'grailsangular.svg'),
apprentice: new Category(name: 'Grails Apprentice', image: 'grailaprrentice.svg'),
async: new Category(name: 'Grails Async', image: 'async.svg'),
devops: new Category(name: 'Grails + DevOps', image: 'grailsdevops.svg'),
googlecloud: new Category(name: 'Grails + Google Cloud', image: 'googlecloud.svg'),
gorm: new Category(name: 'GORM', image: 'gorm.svg'),
ios: new Category(name: 'Grails + iOS', image: 'ios.svg'),
react: new Category(name: 'Grails + React', image: 'react.svg'),
ria: new Category(name: 'Grails + RIA (Rich Internet Application)', image: 'ria.svg'),
testing: new Category(name: 'Grails Testing', image: 'testing.svg'),
vue: new Category(name: 'Grails + Vue.js', image: 'vue.svg'),
weblayer: new Category(name: 'Web Layer', image: 'views.svg'),
Comment on lines +142 to +145
* {@code githubBranch} / {@code githubSlug} on the guide is the version
* with the most recent publication date (or the last-iterated version if
* dates are missing/equal). This drives the "Read More" link in the
* {@code Latest Guides} sidebar.</p>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants